// http english-converting proxy, see RFC 2616
// RFC 3143 Known HTTP Proxy/Caching Problems


#define _CRT_SECURE_NO_WARNINGS

#include <list>
#include <vector>
#include <Winsock2.h>
#include "rs_string.h"
#include "rs_version.h"
#include "rs_log.h"
#include "rs_http_proxy.h"
using namespace std;

#define PROXY_AGENT "http respelling proxy"
#define BUF_SIZE    (256*256)


class tcp_stream {
    // a socket plus an input buffer
public:
    tcp_stream () :
        ts_socket (INVALID_SOCKET),
        ts_in_offset (0),
        ts_peer_closed(false),
        ts_was_opened(false)
    {};
    tcp_stream (SOCKET socket) :
        ts_socket (socket),
        ts_in_offset (0),
        ts_peer_closed(false),
        ts_was_opened(true)
    {};
    void reset_socket ();
    void set_socket (SOCKET socket);
    SOCKET get_socket () const { return ts_socket; }
    int tcp_read ();
    bool tcp_write (const string& data, const rs_string& desc);
    bool read_line (string& header_line);
    bool read_data (string& data, int max_size);
    bool tcp_is_closed () const { return ts_peer_closed; };
    bool tcp_was_opened () const { return ts_was_opened; };
    void tcp_close ();
private:
    SOCKET ts_socket;
    string ts_in_buffer; // data read from socket, first ts_in_offset bytes already processed
    unsigned ts_in_offset; // so many bytes in ts_in_buffer have been read by 
    bool ts_peer_closed; // true after peer requested close
    bool ts_was_opened; // set to true after setting to non-INVALID_SOCKET value, remains true after closing
};


class http_message {
    // either request or response
public:
    http_message (bool is_request, converter& cnv)
        :
        hm_is_request (is_request),
        hm_active (true),
        hm_port_no (0),
        hm_status_no (0),
        hm_headers_parsed(false),
        hm_body_present(false),
        hm_convert(false),
        hm_cnv(cnv)
    {};
    bool serve_message (tcp_stream& is, tcp_stream& os, const rs_string& channel_desc);
    bool is_active () const { return hm_active; };
private:
    bool hm_is_request; // true for request, false for response
    bool hm_headers_parsed; // true after all the headers have been parsed
    bool hm_convert; // true if body is to be converted, e.g for responses of content type text/html
    rs_string hm_content_type; // e.g. L"text/html"; always in lowercase
    rs_string hm_content_encoding; // e.g. L"utf-8"; always in lowercase
    string hm_body_data;  // when hm_convert, body is first completely read into this variable
    vector<string> hm_header_v; // start line and message headers
    string hm_ver; // e.g. "HTTP/1.1" as in request or response
    string hm_method, hm_uri; // first line of request, decomposed (except hm_ver)
    string hm_proto, hm_host, hm_path; // components of hm_uri for request
    unsigned hm_port_no; // numerical value of port for request
    unsigned hm_status_no; // numerical value of response status for response
    string hm_conn_host;  // DNS or IP of connected server host
    unsigned hm_conn_port; // port of connected server
    bool hm_active;  // false when errors on input stream or either stream closed
    bool hm_body_present; // true if message has a body
    int hm_body_size_all; // size of body as calculated from headers
    int hm_body_size_left;  // remaining size of body to be transferred
    rs_string hm_desc; // description string for logging
    converter& hm_cnv; // english converter for messages

    bool parse_http_headers (tcp_stream& stream, const rs_string& channel_desc);
    unsigned get_header_index (const string& name) const;
    string get_header (const string& name) const;
    void set_header (const string& name, const string& value);
    bool http_write_headers (tcp_stream& socket);
    bool send_http_error(tcp_stream& os, const string& status, const string& message);
    void convert_english ();
};


// a stream of http messages from a client to a server and back
// as modified by proxy
class http_stream {
public:
    http_stream (SOCKET client_socket, unsigned id, converter& cnv) :
        hs_request(true, cnv),
        hs_response(false, cnv),
        hs_client(client_socket),
        hs_server(),
        hs_closing(false),
        hs_server_ct(false),
        hs_id(id),
        hs_cnv(cnv)
    {};
    void serve_stream ();
    bool is_closing() const { return hs_closing;};
    bool server_conn_tried () const { return hs_server_ct;};
    void close_tcp_streams();
    rs_string desc(bool is_request) const;
private:
    unsigned hs_id;
    tcp_stream hs_server;
    tcp_stream hs_client;
    http_message hs_request;
    http_message hs_response;
    bool hs_closing;   // no more data to be flushed to streams, only close them
    bool hs_server_ct; // server connection tried
    converter& hs_cnv;
};


struct ipv4_block_perm {
    bool ibp_allow; // if true, address block is allowed; if false, address block is denied
    unsigned char ibp_addr[4];
    unsigned char ibp_mask[4];
    ipv4_block_perm (
        bool allow,
        unsigned char a1,
        unsigned char a2,
        unsigned char a3,
        unsigned char a4,
        unsigned char m1,
        unsigned char m2,
        unsigned char m3,
        unsigned char m4)
    {
        ibp_allow=allow;
        ibp_addr[0] = a1;
        ibp_addr[1] = a2;
        ibp_addr[2] = a3;
        ibp_addr[3] = a4;
        ibp_mask[0] = m1;
        ibp_mask[1] = m2;
        ibp_mask[2] = m3;
        ibp_mask[3] = m4;
    }
};


class http_proxy 
{
public:
    http_proxy (unsigned port_no, converter& cnv);
    void serve_proxy ();
private:
    SOCKET hp_proxy_socket; // on this port proxy listens
    unsigned hp_count;  // for each stream created, it increases by one
    list<http_stream> hp_stream_l;
    vector<ipv4_block_perm> hp_perm_v;
    converter& hp_cnv;
//functions
    void initialize_ip_range_permissions ();
    bool is_ip_allowed(const IN_ADDR& target) const;
};


unsigned int ip_quad_to_int32 (unsigned char a1, unsigned char a2, unsigned char a3, unsigned char a4)
{
    return
        (a1 << 24) |
        (a2 << 16) |
        (a3 <<  8) |
        (a4 <<  0);
}


void split_two (const string& str, char delimiter, string& part1, string& part2, bool all_to_first)
{
    // if delimiter is found, 'str' is divided into 'part1' and 'part2' (delimiter is not included)
    // if not found, str is stored to 'part1' or 'part2' depending on 'all_to_first'
    // and the remaining is empty
    part1 = "";
    part2 = "";
    unsigned pos = str.find (delimiter);
    if (pos == string::npos) {
        if (all_to_first)
            part1 = str;
        else
            part2 = str;
    }
    else {
        part1 = str.substr (0, pos);
        part2 = str.substr (pos+1);
    }

}

/*
 * parses 'uri' into protocol, host, port and path.
  returns true on success and false on error
*/
bool parse_uri(
             const string& uri,
             string& protocol, 
             string& host, 
             string& port, 
             string& path)
{
    protocol = host = port = path = "";
    if (uri.empty())
        return false;
    // Skip white space?
    string rest;
    split_two (uri, ':', protocol, rest, false);
    to_lower (protocol);
    // host and port
    if (rest.substr (0,2) == "//")
        rest = rest.substr (2);
    string host_port;
    split_two (rest, '/', host_port, path, true);
    to_lower (host);
    split_two (host_port, ':', host, port, true);
    if (port.empty()) {
        if (protocol == "http") {
            port = "80";
        }
    }
    return true;
}


int socket_state (SOCKET in_socket, bool check_read=true, bool check_write=false)
{
    if (in_socket == INVALID_SOCKET) {
        rs_log_file (L"socket_state: asked for state of INVALID_SOCKET");
        return INVALID_SOCKET;
    }
    fd_set socket_set;
    FD_ZERO (&socket_set);
    FD_SET (in_socket, &socket_set);
    static const timeval time_zero = {0, 0};
    int result;
    result = select (0,
        (check_read? &socket_set : 0),
        (check_write? &socket_set : 0),
        0, &time_zero);
    if (result < 0) {
        int error_code = WSAGetLastError();
        rs_log_file (L"socket_state: socket" + rs_stringed (in_socket) 
            + L" failed: " + rs_stringed (error_code));
    }
    return result;
}


bool socket_ready (SOCKET in_socket)
{
    int result = socket_state (in_socket);
    return result > 0;
}


bool socket_error (SOCKET in_socket, int *result_ptr=0)
{
    int result = socket_state (in_socket);
    return result < 0;
}


// prepares for passive open (listening port) of specified port_no, but no TCP opening yet
// returns INVALID_SOCKET on error
SOCKET tcp_listening(unsigned port_no)
{
    SOCKET proxy_socket;
    struct sockaddr_in addr;
    int result;

    proxy_socket=socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (proxy_socket == INVALID_SOCKET) {
        result = WSAGetLastError();
        rs_log_console (L"tcp_listening: Cannot create socket");
        return proxy_socket;
    }

    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port_no);
    addr.sin_addr.s_addr = INADDR_ANY;
    if (bind (proxy_socket, (struct sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR ) {
        rs_log_console (L"tcp_listening: bind() failed");
        return INVALID_SOCKET;
    }
    if (listen (proxy_socket, SOMAXCONN) == SOCKET_ERROR ) {
        rs_log_console (L"tcp_listening: listen() failed");
        return INVALID_SOCKET;
    }
    return proxy_socket;
}



SOCKET tcp_connect_server(const char *hostname, unsigned port_no)
{
    SOCKET netd;
    struct hostent  *hp;
    struct sockaddr_in addr;

    rs_log_file(L"tcp_connect_server: connecting to: host "
        + rs_stringed (hostname)
        + L", port " + rs_stringed (port_no));

    if ((hp=gethostbyname(hostname)) == NULL ) {  // todo convert to getaddrinfo
        rs_log_console (L"tcp_connect_server: cannot find host " + rs_stringed (hostname));
        return INVALID_SOCKET;
    }
    if ((netd=socket(AF_INET, SOCK_STREAM, IPPROTO_TCP))<0) {
        rs_log_console (L"tcp_connect_server: cannot create socket");
        return INVALID_SOCKET;
    }

    memset(&addr, 0, sizeof(addr));
    addr.sin_family = hp->h_addrtype;
    addr.sin_port = htons(port_no);
    memcpy(&addr.sin_addr.s_addr, hp->h_addr, hp->h_length);

    if (connect(netd, (struct sockaddr*) &addr, sizeof(addr)) == SOCKET_ERROR) {
        rs_log_console (L"tcp_connect_server: cannot connect " + rs_stringed(hostname));
        return INVALID_SOCKET;
    }
    rs_log_file(L"tcp_connect_server: connected with socket: "
        + rs_stringed (netd));
    return netd;
}


rs_string bin_to_hex (const string& data)
{
    rs_string result, result2;
    const wchar_t hex[]=L"0123456789ABCDEF";
    unsigned i, data_size = data.size();
    for (i=0; i<data_size; i++) {
        unsigned char c = data[i];
        result += hex [c >> 4];
        result += hex[c & 15];
        result += L' ';
        result2 += (c>=32 && c<127 ? wchar_t(c) : L'.');
    }
    return result + result2;
}


void tcp_stream::reset_socket ()
{
    rs_log_file (L"reset_socket to invalid: " + rs_stringed (ts_socket));
    ts_socket = INVALID_SOCKET;
};


void tcp_stream::set_socket (SOCKET socket)
{
    if (socket == INVALID_SOCKET) {
        rs_log_file (L"error, can't set socket set to invalid: " + rs_stringed (ts_socket));
        return;
    }
    ts_was_opened = true;
    //rs_log_file (L"set_socket set to: " + rs_stringed (socket));
    ts_socket = socket;
};


bool tcp_stream::tcp_write (const string& data, const rs_string& desc)
{
    //rs_log_file (L"tcp_write:\n", rs_stringed (data));
    int remaining_len = data.length();  // so many bytes are remaining to be sent
    int sent_len = 0;  // so many bytes have been already sent
    int result;
    while (remaining_len > 0) {
        //rs_log_file (desc + L"tcp_write: sending data, offset "
        //    + rs_stringed (sent_len) + L", length " + rs_stringed (remaining_len));
        result = send (ts_socket, data.substr(sent_len,remaining_len).c_str(), remaining_len, 0);
        if (result == SOCKET_ERROR) {
            tcp_close();
            return false;
        }
        if (result > 0) {
            if (result > remaining_len) {
                rs_log_file (desc + L"tcp_write: high number of sent bytes");
                return false;
            }
            rs_log_file (desc + L"tcp_write: bytes sent ", rs_stringed (result) 
                //+ L": "+ bin_to_hex (data.substr(sent_len,min (remaining_len, 16)))
                );
            remaining_len -= result;
            sent_len += result;
        }
    }
    return true;
}


// asks TCP layer to get the next piece of data into internal structures
// returns SOCKET_ERROR on error (socket closed etc), 1 on data ready
// 0 on no data
int tcp_stream::tcp_read ()
{
    if (ts_socket == SOCKET_ERROR)
        return SOCKET_ERROR;
    int result;
    result = socket_state (ts_socket);
    if (result <= 0)
        return result;
    // otherwise, there are data pending
    char buf[BUF_SIZE+1];
    result = recv (ts_socket, buf, BUF_SIZE, 0);
    if (result == SOCKET_ERROR)
        return SOCKET_ERROR;
    if (result == 0) {
        rs_log_file (L"peer closed tcp connection, let's close it from proxy side, too");
        tcp_close();
        return SOCKET_ERROR;
    }
    buf [result] = '\0';
    if (ts_in_offset == 0)
        ts_in_buffer += string (buf, result);
    else {
        ts_in_buffer = ts_in_buffer.substr (ts_in_offset) + string (buf, result);
        ts_in_offset = 0;
    }
    return 1;
}


// if the start of unread input buffer contains end-of-line marker,
// this region represents line that is extracted
// and function returns true
// otherwise, no complete line is present and function returns false
bool tcp_stream::read_line (string& header_line)
{
    unsigned eol_pos = ts_in_buffer.find ('\n', ts_in_offset);
    if (eol_pos == string::npos)
        return false; // no complete line currently in buffer
    header_line = ts_in_buffer.substr (ts_in_offset, eol_pos - ts_in_offset + 1);
    ts_in_offset = eol_pos + 1;
    if (ts_in_offset == ts_in_buffer.size()) {
        ts_in_offset = 0;
        ts_in_buffer.clear();
    }
    return true;
}


// reads the data from the input buffer of up to the 'max_size' bytes
// returns false on error (socket closed etc)
bool tcp_stream::read_data (string& data, int max_size)
{
    if (ts_in_buffer.empty()) {
        int result = tcp_read();
        if (result < 0)
            return false;
    }
    if (ts_in_offset == 0) {
        int avail_size = ts_in_buffer.size();
        int used_size = min (max_size, avail_size);
        if (used_size == avail_size) {
            data = ts_in_buffer;
            ts_in_offset = 0;
            ts_in_buffer.clear();
        } else {
            data = ts_in_buffer.substr (0, used_size);
            ts_in_offset = used_size;
        }
    } else {
        int avail_size = ts_in_buffer.size() - ts_in_offset;
        int used_size = min (max_size, avail_size);
        data = ts_in_buffer.substr (ts_in_offset, used_size);
        ts_in_offset += used_size;
        if (ts_in_offset == ts_in_buffer.size()) {
            ts_in_offset = 0;
            ts_in_buffer.clear();
        }
    }
    return true;
}


void tcp_stream::tcp_close ()
{
    if (ts_socket != INVALID_SOCKET) {
        rs_log_file (L"tcp_close: socket " + rs_stringed (ts_socket));
        shutdown(ts_socket, SD_BOTH);
        closesocket(ts_socket);
        ts_socket = INVALID_SOCKET;
    }
}



// parses HTTP headers until
// either enough data has arrived to complete a header list for message (returns true)
// or not enough data or error has occured or tcp stream has been closed (returns false)
bool http_message::parse_http_headers (tcp_stream& stream, const rs_string& channel_desc)
{
    //rs_log_file (L"parse_http_headers: " + rs_stringed (stream.get_socket()) +
    //    (hm_is_request? L" request" : L" response"));
    while (1) {
        string header_line;
        if (!stream.read_line (header_line)) {
            if (!socket_ready (stream.get_socket()))
                return false;
            int result = stream.tcp_read ();
            if (result <= 0)
                return false;
            continue;
        }
        unsigned header_len = header_line.length();
        if (header_len>= 2 && header_line[header_len-2] =='\r'  && header_line[header_len-1] =='\n')
            header_line = header_line.substr (0, header_len-2);
        else if (header_len>= 1 && header_line[header_len-1] =='\n')
            header_line = header_line.substr (0, header_len-1);
        if (header_line.empty()) {
            hm_headers_parsed = true;
            break;
        } else {
            hm_header_v.push_back (header_line);
            rs_log_file (hm_desc + L"parsed header: " + rs_stringed (header_line));
        }

    }
    // after complete headers are received, some postprocessing
    istringstream ifs_line (hm_header_v[0]);
    if (hm_is_request) {
        string port_str;
        ifs_line >> hm_method >> hm_uri >> hm_ver;
        parse_uri(hm_uri, hm_proto, hm_host, port_str, hm_path);
        hm_port_no = atoi(port_str.c_str());
    } else {
        string status_str;
        ifs_line >> hm_ver >> status_str;
        hm_status_no = 0;
        hm_status_no = atoi(status_str.c_str());
    }
    // calculate hm_body_present, rfc 2616, section 4.3 Message Body
    // calculate hm_body_size_all
    hm_body_size_all = 0;
    hm_body_size_left = 0;
    if (hm_is_request) {
        hm_body_present = false;
        if (get_header_index ("Content-Length") > 0  ||
            get_header_index ("Transfer-Encoding") > 0)
            hm_body_present = true;
        //if (hm_host.find ("upload.wikimedia.org") == string::npos)
        set_header ("Accept-Encoding", "identity");
        // until compression/decompression libraries are available,
        // only uncompressed data are to be sent from server

        // todo remember HEAD requests as these don't have bodies in responses
    } else {
        // response
        // todo: after HEAD id implemented, hm_body_present = false if this is response for HEAD
        hm_body_present = true;
        if (hm_status_no == 204 || hm_status_no == 304 || hm_status_no < 200)
            hm_body_present = false;
    }
    if (hm_body_present) {
        string content_size_str = get_header ("Content-Length");
        if (content_size_str.empty())
            hm_body_size_all = 0;
        else
            hm_body_size_all = atoi(content_size_str.c_str());
        hm_body_size_left = hm_body_size_all;
        rs_log_file (hm_desc + L"parse_http_headers: hm_body_size_all=" + rs_stringed (hm_body_size_all));
    }
    string content_type = get_header ("Content-Type"); // text/html; charset=utf-8
    hm_content_type = nth_substring (rs_stringed(content_type), 0, L";");
    to_lower (hm_content_type);
    hm_content_encoding = nth_substring (rs_stringed(content_type), 1, L";");
    hm_content_encoding = nth_substring (hm_content_encoding, 1, L"=");
    to_lower (hm_content_encoding);
    hm_convert = hm_content_type == L"text/html";
    rs_log_file (hm_desc + L"parse_http_headers: hm_body_present=" +
        (hm_body_present? L"T" : L"F") +
        L", hm_headers_parsed=" + (hm_headers_parsed?L"T" : L"F"));
    return hm_headers_parsed;
}


// returns HTTP header index (>=0) given its 'name' (case-insensitive)
// if such header exists (the first such header if more than one
// returns 0 if such header doesn't exist
unsigned http_message::get_header_index (const string& name) const
{
    string lc_name (name);
    to_lower (lc_name);
    unsigned header_size = hm_header_v.size();
    for (unsigned i=1; i< header_size; i++) {
        unsigned colon_pos = hm_header_v[i].find (':');
        if (colon_pos == string::npos)
            continue; // todo: report error "incorrect header" if no initial white space
        string header_name (hm_header_v[i].substr (0, colon_pos));
        to_lower (header_name);
        if (lc_name == header_name) {
            return i;
        }
    }
    return 0;
}

// returns HTTP header given its 'name' (case-insensitive)
string http_message::get_header (const string& name) const
{
    unsigned hix = get_header_index (name);
    if (hix == 0)
        return "";
    const string& header = hm_header_v[hix];
    unsigned value_pos = header.find (':') + 1;
    unsigned max_pos = header.length() -1;
    while (value_pos < max_pos &&
        (header[value_pos]==' ' || header[value_pos]=='\t'))
        value_pos++;
    return header.substr (value_pos);
}


// sets HTTP header to specified value
void http_message::set_header (const string& name, const string& value)
{
    unsigned hix = get_header_index (name);
    string header = name + ": " + value;
    if (hix == 0) {
        hm_header_v.push_back (header);
    } else {
        hm_header_v[hix] = header;
    }
    rs_log_file (hm_desc + L"set_header: ", rs_stringed (name) + L": " + rs_stringed(value));
}


// converts hm_body_data from TO to RO (for responses)
void http_message::convert_english ()
{
    if (!hm_body_present)
        return;
    if (hm_is_request)
        return;
    if (hm_content_type!= L"text/html"  && hm_content_type!= L"text/plain")
        return;
    // converting only responses with body
    rs_string ifname (L"respell_http_to_file.tmp");
    rs_string ofname (L"respell_http_ro_file.tmp");
    // todo don't store data to files in order to convert them
    {
        // block is present to limit the scope of variables
        rs_ostream ofs (ifname);
        ofs.write (hm_body_data.c_str(), hm_body_data.length());
    }
    bool converted (false);
    if (hm_content_type == L"text/html") {
        converted = hm_cnv.convert_file (ifname, ofname, L"htm", false);
    } else {
        converted = hm_cnv.convert_file (ifname, ofname, L"txt", false);
    }
    if (!converted)
        return;
    {
        // block is present to limit the scope of variables
        rs_istream ifs (ofname);
        rs_string converted_text;
        ifs.read_file (converted_text);
        wstring_to_utf8 (converted_text, hm_body_data);
    }
}


bool http_message::http_write_headers (tcp_stream& stream)
{
    string all_headers;
    unsigned header_size = hm_header_v.size();
    for (unsigned i=0; i< header_size; i++)
        all_headers += hm_header_v[i] + "\r\n";
    // rs_log_file (L"http_write_headers: ", rs_stringed (all_headers));
    //rs_log_file (L"http_write_headers: ", rs_stringed (hm_header_v[0]));
    all_headers += "\r\n";
    if (!stream.tcp_write (all_headers, hm_desc))
        return false;
    return true;
}


bool http_message::send_http_error(tcp_stream& os, const string& status, const string& message)
{
    rs_log_console (L"send_http_error: "+ rs_stringed (status));
    string reply = "HTTP/1.1 " + status + " " + message +"\r\n"
        "MIME-version: 1.0\r\nContent-type: text/html\r\n\r\n" 
        "<HEAD><TITLE>" PROXY_AGENT "Warning Message" "</TITLE></HEAD>\r\n"
        "<BODY>\r\n<H1>" + status + " " + message +"</H1>\r\n\r\n<HR>\r\n<em>"
        PROXY_AGENT
        " v" RS_VERSION_BUILD
        "</em>\r\n</BODY>\r\n";
    bool result = os.tcp_write (reply, hm_desc);
    return result;
}


// transfers and modifies message from 'is' to 'os'
// as much data as already received
// returns true if there is possibility that data are still to be processed
// i.e. it makes sense to call it again immediately without waiting for new data to arrive
// returns false when all received input data have been processed
bool http_message::serve_message (tcp_stream& is, tcp_stream& os, const rs_string& channel_desc)
{
    if (!hm_active)
        return false;
    if (hm_desc != channel_desc)
        hm_desc = channel_desc;
    int sstate = socket_state (is.get_socket());
    if (sstate < 0) {
        rs_log_file (hm_desc + L"serve_message: input stream finished");
        hm_active = false;
        return false;
    }
    bool message_sent (false);

    if (!hm_headers_parsed) {
        //rs_log_file (L"serve_message " + channel_desc + L"checking for headers");
        if (!parse_http_headers (is, channel_desc)) {
            //rs_log_file (L"serve_message " + channel_desc + L"headers not yet complete");
            return false;
        }

        if (hm_is_request) {
            if (hm_proto != "http" ) {
                rs_log_file(hm_desc + L"serve_message: invalid protocol: "
                    + rs_stringed (hm_proto));
                hm_active = false;
                return false;
            }
            if (os.get_socket() == INVALID_SOCKET) {
                // connect to server the first time
                os.set_socket (tcp_connect_server(hm_host.c_str(), hm_port_no));
                
                if (os.get_socket() == INVALID_SOCKET) {
                    rs_log_file(hm_desc + L"serve_message: cannot connect to: host "
                        + rs_stringed (hm_host)
                        + L", port " + rs_stringed (hm_port_no));
                    hm_active = false;
                    return false;
                }
                rs_log_file(hm_desc + L"serve_message: connected to: host "
                    + rs_stringed (hm_host)
                    + L", port " + rs_stringed (hm_port_no));
                hm_conn_host = hm_host;
                hm_conn_port = hm_port_no;
            } else {
                // verify that the server data are still the same
                if (hm_conn_host != hm_host  ||
                    hm_conn_port != hm_port_no) {
                        rs_log_file (hm_desc +
                            L"serve_message: cannot send requests to different "+
                            L"server, closing stream");
                    hm_active = false;
                    return true;
                }
            }
        }

        if (!http_write_headers (os)) {
            hm_active = false;
            return false;
        }
        rs_log_file (hm_desc + L"serve_message: sent headers");
        if (!hm_is_request && get_header ("Connection") == "close") {
            hm_active = false;
            rs_log_file (hm_desc + L"serve_message: header Connection close");
            return false;
        }
    }
    if (hm_headers_parsed && hm_body_present) {
        // send response from server to client
        while (hm_body_size_left > 0   &&   socket_state (is.get_socket()) >= 0) {
            string data;
            if (!is.read_data (data, hm_body_size_left))
                break;
            if (data.size() == 0)
                break;
            hm_body_size_left -= data.size();
            //rs_log_file (hm_desc + L"serve_message: body transfer, read "
            //    + rs_stringed (data.size()) + L" bytes, remaining "
            //    + rs_stringed (hm_body_size_left) + L" bytes");
            if (hm_convert) {
                hm_body_data += data;
            } else {
                if (!os.tcp_write (data, hm_desc)) {
                    rs_log_file (hm_desc + L"serve_message: cannot write body");
                    hm_active = false;
                    break;
                }
            }
        }
        if (hm_body_size_left == 0) {
            if (hm_convert) {
                convert_english();
                if (!os.tcp_write (hm_body_data, hm_desc)) {
                    rs_log_file (hm_desc + L"serve_message: cannot write converted body");
                    hm_active = false;
                }
            }
            rs_log_file (hm_desc + L"serve_message: body transfer finished");
        } else {
            rs_log_file (hm_desc + L"serve_message: body transfer, remaining "
                + rs_stringed (hm_body_size_left) + L" bytes");
        }
    }
    if (hm_headers_parsed && hm_body_size_left == 0) {
        message_sent = true;
        hm_header_v.clear();
        hm_headers_parsed = false;
        hm_body_size_all = 0;
        rs_log_file (hm_desc + L"serve_message: reset");

    }
    return message_sent;
}


// processes all new data (if any) from both tcp streams 
void http_stream::serve_stream()
{
    if (hs_client.get_socket() == INVALID_SOCKET) {
        rs_log_file (desc(true) + L"serve_stream: closing due to closed client connection");
        hs_closing = true;
        return;
    }
    if (hs_server.get_socket() == INVALID_SOCKET  && hs_server.tcp_was_opened()) {
        rs_log_file (desc(true) + L"serve_stream: closing due to closed server connection");
        hs_closing = true;
        return;
    }
    while (true) {
        if (!hs_request.is_active()) {
            rs_log_file (desc(true) + L"serve_stream: closing due to inactive request message");
            hs_closing = true;
            break;
        }
        if (!hs_request.serve_message (hs_client, hs_server, desc(true)))
            break;
    }
    while (true) {
        if (!hs_response.is_active()) {
            rs_log_file (desc(true) + L"serve_stream: closing due to inactive response message");
            hs_closing = true;
            break;
        }
        if (!hs_response.serve_message (hs_server, hs_client, desc(false)))
            break;
    }
}


rs_string http_stream::desc(bool is_request) const
{
    SOCKET server_socket = hs_server.get_socket();
    rs_string result = L"(" + rs_stringed (hs_id) + L"|c" 
        + rs_stringed (hs_client.get_socket())
        + (is_request? L">" : L"<") + L"s"
        + (server_socket != INVALID_SOCKET ? rs_stringed (server_socket) : L"-")
        + L") ";
    return result;
}


// closes all open tcp streams
void http_stream::close_tcp_streams()
{
    hs_client.tcp_close();
    hs_server.tcp_close();
}


// initializes vector ipv4_addr_mask_v.
void http_proxy::initialize_ip_range_permissions ()
{
    ipv4_block_perm ipb_localhost (true, 127,0,0,1,  0,0,0,1);
    // right now, only localhost is allowed
    hp_perm_v.push_back (ipb_localhost);
    // all other are prohibited
    ipv4_block_perm ipb_all (false, 0,0,0,0,  255,255,255,255);
    hp_perm_v.push_back (ipb_all);
}


// returns true if 'target' IPv4 address is allowed to be processed
bool http_proxy::is_ip_allowed(const IN_ADDR& target) const
{
    int i, perm_size;
    perm_size = hp_perm_v.size();

    bool match= false;
    for(i=0;  i<perm_size;  i++) {
        const ipv4_block_perm& bl= hp_perm_v[i];
        unsigned addr = ip_quad_to_int32 (
            bl.ibp_addr[0], bl.ibp_addr[1], bl.ibp_addr[2], bl.ibp_addr[3]);
        unsigned mask = ip_quad_to_int32 (
            bl.ibp_mask[0], bl.ibp_mask[1], bl.ibp_mask[2], bl.ibp_mask[3]);
        unsigned tgt = target.S_un.S_addr;

        bool matches = (tgt & mask) == (addr & mask);
        if (matches)
            return bl.ibp_allow;
    }
    return false;
}


http_proxy::http_proxy (unsigned port_no, converter& cnv) :
    hp_cnv (cnv)
{
    hp_count = 0;
    initialize_ip_range_permissions ();
    hp_proxy_socket = tcp_listening(port_no);
    if (hp_proxy_socket == INVALID_SOCKET) {
        rs_report_error (L"http_proxy: Port not available: port " + rs_stringed(port_no));
    } else {
        rs_log_console (L"http_proxy: serving at port " + rs_stringed(port_no));
    }
};


// checks all the sockets and processes all the incoming data
void http_proxy::serve_proxy ()
{
    while (socket_ready (hp_proxy_socket)) {
        //rs_log_file (L"serve_proxy: check for new incoming connections");
        SOCKET client_socket;
        struct sockaddr_in from;
        int len = sizeof(from);
        client_socket=accept(hp_proxy_socket, (struct sockaddr*)&from, &len);
        if (client_socket == INVALID_SOCKET) {
            int result = WSAGetLastError();
            rs_log_console (L"serve_proxy: cannot accept request, error " + rs_stringed (result));
            break;
        }
        if (!is_ip_allowed(from.sin_addr)) {
            rs_log_console (L"serve_proxy: client not authorized");
            shutdown(client_socket, SD_BOTH);
            closesocket(client_socket);
            continue;
        }
        hp_count++;
        hp_stream_l.push_back (http_stream (client_socket, hp_count, hp_cnv));
        rs_log_file (hp_stream_l.back().desc(true) +
            L"serve_proxy: new http stream");
    }
    list<http_stream>::iterator hit, nit;
    //rs_log_file (L"http_proxy::serve_proxy: processing connections");
    for (hit = hp_stream_l.begin();  hit != hp_stream_l.end();  hit=nit) {
        //rs_log_file (L"http_proxy::serve_proxy: connection " + hit->desc() );
        nit = hit;
        nit++;
        if (hit->is_closing()) {
            rs_log_file (hit->desc(true) +
                L"serve_proxy: closing http stream");
            hit->close_tcp_streams();
            hp_stream_l.erase (hit);
        } else {
            hit->serve_stream();
        }
    }
}


bool rs_http_proxy(int port_no, converter& cnv)
{
    WSADATA wsa_data;
    int result = WSAStartup(2, &wsa_data);
    if (result != 0) {
        rs_report_error (L"Can't initialize proxy functions.");
        return false;
    }
    http_proxy hp_main (port_no, cnv);
    while (1) {
        hp_main.serve_proxy ();
        Sleep (15);
    }
    return true;
}
